package gae.testjig.ofy.dao;

import gae.testjig.ofy.dto.OSeat;
import gae.testjig.ofy.dto.OSeatsRoot;
import gae.testjig.server.ChannelLogger;
import gae.testjig.server.DuplicateException;

import java.util.ConcurrentModificationException;

import com.google.appengine.api.datastore.KeyFactory;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Objectify;
import com.googlecode.objectify.ObjectifyService;
import com.googlecode.objectify.Query;

public class ODaoSeats {
	
	static private Key<OSeatsRoot> seatsRootKey;
	static {
		ObjectifyService.register(OSeatsRoot.class);
		ObjectifyService.register(OSeat.class);
		
		Objectify ofy = ObjectifyService.begin();
		OSeatsRoot rootEntity = new OSeatsRoot("seats_root_key_name");
		ofy.put(rootEntity);
		seatsRootKey = new Key<OSeatsRoot>(OSeatsRoot.class, rootEntity.getEntityId());
	}
	
	static public void reserveSeat(ChannelLogger logger, String ownerName, String seatId) throws DuplicateException {
		logger.info(ownerName+": BEGIN");
		for (int i=0;i<10;i++) {
			try {
				reserveSeatAttempt(logger, ownerName, seatId);
				logger.info(ownerName+": END");
				return; // we get here if reservation succeeds
			}
			catch (ConcurrentModificationException cme) {
				logger.info(ownerName+": EXCEPTION java.util.ConcurrentModificationException");
				// stay in the loop and try again.
			}
			// you could use another backoff algorithm here rather than 100ms each time.
			try { Thread.sleep(100); } catch (InterruptedException e) {}
		}
		logger.info(ownerName+": ABORT");
		throw new ConcurrentModificationException("failed to reserve seat "+seatId);
	}

	static private void reserveSeatAttempt(ChannelLogger logger, String ownerName, String seatId) throws DuplicateException, ConcurrentModificationException {
		logger.info(ownerName+": beginning transaction.");
		Objectify ofy = ObjectifyService.beginTransaction();
		logger.info(ownerName+": transactionId="+ofy.getTxn().getId());

		try {
			logger.info(ownerName+": test for existence of entity with seatId=" + seatId);
			Query<OSeat> testExistsQuery = ofy.query(OSeat.class).ancestor(seatsRootKey).filter("seatId =", seatId);
			OSeat exists = testExistsQuery.get();
			if ( exists != null ) {
				logger.info(ownerName+": sorry, that seat has already been taken.");
				throw new DuplicateException("seatId "+seatId+" already exists.");
			} else {
				logger.info(ownerName+": that seat has not been taken yet.");
				logger.info(ownerName+": create the seat entity with seatId=" + seatId);
				OSeat seat = new OSeat(seatsRootKey, seatId, ownerName);
				Key<OSeat> key = ofy.put(seat);
				logger.info(ownerName+": created seat. entity key = " + KeyFactory.keyToString(key.getRaw()));
				ofy.getTxn().commit(); // throws java.util.ConcurrentModificationException if entity group was modified by other thread
				logger.info(ownerName+": transaction committed.");
			}
		}
		finally {
			if (ofy.getTxn().isActive()) {
		        ofy.getTxn().rollback();
		    }
		}
	}
	
	static public String fetchSeatInfo(String seatId) {
		Objectify ofy = ObjectifyService.begin();
		Query<OSeat> testExistsQuery = ofy.query(OSeat.class).filter("seatId =", seatId);
		OSeat exists = testExistsQuery.get();
		if ( exists != null ) {
			return "Found seat entity: seatId="+exists.getSeatId()+", ownerName="+exists.getOwnerName()+", timestamp="+exists.getTimeStamp();
		} else {
			return "There is no entity in the datastore with seatId="+seatId;
		}
	}
	
	static public void deleteSeatEntity(String seatId) {
		Objectify ofy = ObjectifyService.begin();
		Key<OSeat> key = new Key<OSeat>(seatsRootKey, OSeat.class, seatId);
		ofy.delete(key);
	}
	
}
